# -*- coding: utf-8 -*-

"""
对日期的封装
"""
import datetime
import time
from dateutil.relativedelta import relativedelta
from dateutil import parser

DEFAULT_FORMAT = '%Y-%m-%d %H:%M:%S'  # 默认时间格式
DEFAULT_DATE_FORMAT = '%Y-%m-%d'  # 默认日期格式
DEFAULT_MONTH_FORMAT = '%Y-%m'  # 默认月份格式

CONFIG = {
    'format_str': DEFAULT_FORMAT,  # {string} 时间格式化的格式字符串
    'format_list': (DEFAULT_FORMAT, '%Y-%m-%d %H:%M:%S.%f', DEFAULT_DATE_FORMAT,
                    '%Y年%m月%d日 %H时%M分%S秒', '%Y年%m月%d日　%H时%M分%S秒', '%Y年%m月%d日 %H时%M分', '%Y年%m月%d日　%H时%M分',
                    '%Y年%m月%d日 %H:%M:%S', '%Y年%m月%d日　%H:%M:%S', '%Y年%m月%d日 %H:%M', '%Y年%m月%d日　%H:%M', '%Y年%m月%d日',
                    '%Y/%m/%d %H:%M:%S', '%Y/%m/%d %H:%M:%S.%f', '%Y/%m/%d', '%Y%m%d', '%Y%m%d%H%M%S',
                    '%Y/%m/%d %H:%M', '%Y-%m-%d %H:%M', "%Y-%m-%dT%H:%M",
                    "%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M:%S.%fZ", "%Y-%m-%dT%H:%M:%S+08:00",
                    "%Y-%m-%dT%H:%M:%S.%f+08:00",),
}

class DateUtilManager(object):

    @classmethod
    def to_datetime(cls, value, format_str=None, is_now=False):
        """
        @desc 字符串转成datetime格式
        :param value:
        :param format_str:
        :param is_now:
        :return:
        """
        if not value:
            return datetime.datetime.now() if is_now else value
        # datetime
        elif isinstance(value, datetime.datetime):
            return value
        # datetime.date (与 datetime.datetime 类型不同,它不能添加时分秒)
        elif isinstance(value, datetime.date):
            return datetime.datetime(value.year, value.month, value.day)
        # time
        elif isinstance(value, time.struct_time):
            return datetime.datetime(*value[:6])  # 只能精确到秒,无法精确到毫秒级别
        # 字符串
        elif isinstance(value, str):
            if format_str:
                try:
                    return datetime.datetime.strptime(value, format_str)
                except Exception:
                    pass
            global CONFIG
            for fm in CONFIG.get('format_list'):
                try:
                    return datetime.datetime.strptime(value, fm)
                except Exception:
                    pass
        # 纯数值类型,认为是时间戳(要注意此处的时间不能早于1970-01-01 00:00)
        if isinstance(value, (int, float)):
            try:
                return datetime.datetime.fromtimestamp(value)
            except Exception:
                pass
        # 其它类型,无法格式化
        return None

    @classmethod
    def to_date(cls, value, format_str=None, is_now=False):
        """
        @desc 转换成date格式
        :param value:
        :param format_str:
        :param is_now:
        :return:
        """
        if not value:
            return datetime.date.today() if is_now else value
        # datetime
        elif isinstance(value, datetime.datetime):
            return value.date()
        # datetime.date
        elif isinstance(value, datetime.date):
            return value
        # time
        elif isinstance(value, time.struct_time):
            return datetime.date(*value[:3])  # 只能精确到秒,无法精确到毫秒级别
        # 字符串
        elif isinstance(value, str):
            value = cls.to_datetime(value, format_str=format_str)
            if value is None:
                return None
            return datetime.date(value.year, value.month, value.day)
        # 纯数值类型,认为是时间戳
        if isinstance(value, (int, float)):
            try:
                return datetime.date.fromtimestamp(value)
            except Exception:
                pass
        # 其它类型,无法格式化
        return None

    @classmethod
    def to_time(cls, value=None, format_str=None, is_now=False):
        """
        @desc 转换成time对象
        :param value:
        :param format_str:
        :param is_now:
        :return:
        """
        if not value:
            return time.localtime() if is_now else value
        # time
        elif isinstance(value, time.struct_time):
            return value
        # datetime, datetime.date
        elif isinstance(value, (datetime.datetime, datetime.date)):
            return value.timetuple()
        # 纯数值类型,认为是时间戳
        elif isinstance(value, (int, float)):
            try:
                return time.localtime(value)
            except Exception:
                pass
        # 字符串
        elif isinstance(value, str):
            if format_str:
                return time.strptime(value, format_str)
            global CONFIG
            for fm in CONFIG.get('format_list'):
                try:
                    return time.strptime(value, fm)
                except:
                    pass
        # 其它类型,无法格式化
        return None

    @classmethod
    def sub_date(cls, dt_str1, years=0, months=0, days=0, weeks=0, year=None, month=None, day=None, weekday=None,
                 symbol="add"):
        """
        @desc 计算date日期类型
        :param dt_str1: 初始日期
        :param years: 多少年
        :param months: 多少月
        :param days: 多少日
        :param weeks: 多少周
        :param year: 年份
        :param month: 月份
        :param day: 天
        :param weekday: 周
        :param symbol: 计算行为
        :return:
        """
        if not dt_str1:
            raise ValueError("日期不允许为空！")
        if symbol not in {"add", "reduce"}:
            raise ValueError("symbol must belong to 'add' or 'reduce'")
        func = relativedelta(years=years, months=months, days=days, weeks=weeks,
                             year=year, month=month, day=day, weekday=weekday)
        if symbol == "add":
            return cls.to_date(dt_str1) + func
        else:
            return cls.to_date(dt_str1) - func


    @classmethod
    def date_time_to_str(cls, value, format_str=DEFAULT_DATE_FORMAT, is_now=False):
        """
        @desc date or time or datetime日期转成日期字符串
        :param value:
        :return: 格式化后的时间字符串
        """
        if not value:
            value = datetime.datetime.now() if is_now else value
        # 字符串类型,直接返回
        elif isinstance(value, str):
            value = cls.to_datetime(value)
        # 纯数值类型,认为是时间戳
        elif isinstance(value, (int, float)):
            value = cls.to_time(value)

        # datetime, datetime.date
        if isinstance(value, (datetime.datetime, datetime.date)):
            try: # 防止出现1900年以前日期造成的解析错误
                return value.strftime(format_str)
            except Exception:
                return str(value)
        # time
        elif isinstance(value, time.struct_time):
            try:
                return time.strftime(format_str, value)
            except Exception:
                pass
        # 其它类型,无法格式化
        return None

    @classmethod
    def to_timestamp(cls, value=None, format_str=None, is_now=False):
        """
        将时间转成时间戳(单位:秒)
        注: 只能精确到秒,无法精确到毫秒级别
        :param {time|datetime.datetime|datetime.date|int|long|float|string} value: 原时间(为空则默认为当前时间；纯数值则认为是时间戳,单位:秒)
        :param {string} format_str: 日期格式化的格式字符串(默认为: %Y-%m-%d %H:%M:%S)
        :return {float}: 对应的时间戳(单位:秒)
        """
        if not value:
            return time.time() if is_now else value
        # 纯数值类型,认为是时间戳
        elif isinstance(value, (int, float)):
            return value
        # time
        elif isinstance(value, time.struct_time):
            return time.mktime(value)
        # datetime, datetime.date
        elif isinstance(value, (datetime.datetime, datetime.date)):
            return time.mktime(value.timetuple())
        # 字符串
        elif isinstance(value, str):
            value = cls.to_time(value, format_str=format_str)  # 先转成 time 类型再处理
            if value is None:
                return None
            return time.mktime(value)
        # 其它类型,无法格式化
        return None

    @classmethod
    def sub_datetime(cls, dt_str1, years=0, months=0, days=0, weeks=0,
                 hours=0, minutes=0, seconds=0, microseconds=0,
                 year=None, month=None, day=None, weekday=None,
                 hour=None, minute=None, second=None, microsecond=None, symbol="add"):
        """
        @desc 对日期进行计算
        :param dt_str1: 时间字符串或日期对象,必须%Y-%m-%d格式
        :param years: 年的数字
        :param months: 月的数字
        :param days: 天的数字
        :param weeks: 周的数字
        :param hours: 小时数
        :param minutes: 分钟数
        :param seconds: 秒数
        :param microseconds: 微秒数
        :param year: 指定年
        :param month: 指定月
        :param day: 指定天
        :param weekday: 指定周
        :param hour: 指定小时
        :param minute: 指定分钟
        :param second: 指定秒
        :param microsecond: 指定微秒
        :param symbol:计算符号，add为加，reduce为减
        :return:
        """
        if not dt_str1:
            raise ValueError("日期不允许为空！")
        if symbol not in {"add", "reduce"}:
            raise ValueError("symbol must belong to 'add' or 'reduce'")
        func = relativedelta(years=years, months=months, days=days, weeks=weeks,
                             hours=hours, minutes=minutes, seconds=seconds, microseconds=microseconds,
                             year=year, month=month, day=day, weekday=weekday,
                             hour=hour, minute=minute, second=second, microsecond=microsecond)
        if symbol == "add":
            return cls.to_datetime(dt_str1) + func
        else:
            return cls.to_datetime(dt_str1) - func

date_func = DateUtilManager